package hyl.base.medium.grab;

import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;
import static org.bytedeco.ffmpeg.global.swscale.*;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;

import org.bytedeco.ffmpeg.avcodec.AVCodec;
import org.bytedeco.ffmpeg.avcodec.AVCodecContext;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.avutil.AVDictionary;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.ffmpeg.swscale.SwsContext;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Pointer;
import org.bytedeco.javacpp.PointerPointer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.bytedeco.javacv.FFmpegFrameRecorder.SeekCallback;
//import org.bytedeco.javacv.FFmpegFrameRecorder.WriteCallback;
import org.bytedeco.javacv.FrameRecorder.Exception;

import hyl.base.medium.MyImage;
import hyl.core.run.IDoEachT1;
import hyl.core.run.IFilter;

public class RtspGrabber {
	/*
	 * 模拟 ffmpeg 的 采集器
	 */

	static final Logger log = LoggerFactory.getLogger("RtspGrabber");
	// 输入设备 视频文件或视频流上下文
	AVFormatContext 输入引擎 = null;

	// 解码器的上下文,除了解码器,还有解码时候需要用到的其他额外信息
	AVCodecContext 视频解码器 = null;
	AVCodecContext 音频解码器 = null;
	// 图像变形器
	SwsContext 图像变形器 = null;
	// 输入流
	AVDictionary 视频解码器配置项 = new AVDictionary(null);
	AVDictionary 音频解码器配置项 = new AVDictionary(null);

	// 视频流所在通道
	int i图像输出格式 = AV_PIX_FMT_BGR24;
	int i输出宽 = 1280;
	int i输出高 = 720;
	int i视频间隔帧 = 1;
	// packet 用于存放从流中取到的帧
	AVPacket packet = null;
	AVPacket packet2 = null;
	// pFrame 用于存放avpacket 解码后的 avframe
	AVFrame frame = null;// 用c 声明 分配帧内存
	// pframeRGB 存放解码帧变换输出的 RGB格式帧
	AVFrame frameRGB = null;// 用c 声明 分配帧内存

	String s输入路径;
	IFilter<BufferedImage> fl图像过滤器 = null;
	IDoEachT1<AVStream, AVFrame> fl图像帧过滤器 = null;
	IDoEachT1<AVStream, AVFrame> fl音频帧过滤器 = null;
	IFilter<AVPacket> av包过滤器 = null;

	public RtspGrabber(final String 打开路径) {
		s输入路径 = 打开路径;

		try {
			step1();
		} catch (Exception e) {
			e.printStackTrace();
			release();
		}
		step3_构造变形器();
	}

	public AVFormatContext get引擎() {
		return 输入引擎;
	}

	/**
	 * 只能一个过滤器,多个会导致图像处理时间太久,影响视频采集时间同步
	 * 
	 * @param 过滤器
	 * @return
	 */
	public RtspGrabber load图像帧过滤器(IDoEachT1<AVStream, AVFrame> 过滤器) {
		fl图像帧过滤器 = 过滤器;
		return this;
	}

	public RtspGrabber load图像过滤器(IFilter<BufferedImage> 过滤器) {
		fl图像过滤器 = 过滤器;
		return this;
	}

	/**
	 * 只能一个过滤器,多个会导致图像处理时间太久,影响视频采集时间同步
	 * 
	 * @param 过滤器
	 * @return
	 */
	public RtspGrabber load音频过滤器(IDoEachT1<AVStream, AVFrame> 过滤器) {
		fl音频帧过滤器 = 过滤器;
		return this;
	}

	/**
	 * 只能一个过滤器,多个会导致包处理时间太久,影响视频采集时间同步
	 * 
	 * @param 包过滤器
	 * @return
	 */
	public RtspGrabber load包过滤器(IFilter<AVPacket> 包过滤器) {
		av包过滤器 = 包过滤器;
		return this;
	}

	public RtspGrabber load视频解码配置项(Map<String, String> 自定义属性) {
		if (自定义属性 == null || 自定义属性.isEmpty())
			return this;
		for (Entry<String, String> e : 自定义属性.entrySet()) {
			av_dict_set(视频解码器配置项, e.getKey(), e.getValue(), 0);
		}
		return this;
	}

	public RtspGrabber load音频解码配置项(Map<String, String> 自定义属性) {
		if (自定义属性 == null || 自定义属性.isEmpty())
			return this;
		for (Entry<String, String> e : 自定义属性.entrySet()) {
			av_dict_set(音频解码器配置项, e.getKey(), e.getValue(), 0);
		}
		return this;
	}

	public RtspGrabber set输出图像参数(int 图像输出格式, int 间隔帧, int 输出宽, int 输出高) {
		if (图像输出格式 > 0)
			this.i图像输出格式 = 图像输出格式;
		if (间隔帧 > 0)
			i视频间隔帧 = 间隔帧;
		if (输出宽 > 0)
			i输出宽 = 输出宽;
		if (输出高 > 0)
			i输出高 = 输出高;
		return this;
	}

	public void start() {
		try {
			step4();
		} catch (Exception e) {
			e.printStackTrace();
		}
		release();

	}

	/**
	 * 采集
	 * 
	 * @return
	 * @throws Exception
	 * @throws IOException
	 */
	void step1() throws Exception {

		if (this.frame != null)
			return;
		输入引擎 = new AVFormatContext(null);
		// int 输入格式 = AV_PIX_FMT_YUVJ420P;

		// ssl时才需要开启网络解析
		// avformat_network_init();
		// 读取视频文件
		// 主要就是根据url创建InputStream，并且会根据不同协议（rtmp/rtsp/hls/文件等）尝试读取一些文件头数据（流媒体头数据）。

		if (avformat_open_input(输入引擎, s输入路径, null, null) != 0) {
			error("无法打开" + s输入路径); // Couldn't open file

		}
		// 查找视频信息
		if (avformat_find_stream_info(输入引擎, (PointerPointer<Pointer>) null) < 0) {
			error("无法发现视频流信息"); // Couldn't find stream information
		}
		/*
		 * 打印有关输入或输出格式的详细信息，例如<br> 持续时间，比特率，流，容器，程序，元数据，边数据，<br> 编解码器和时基。
		 */
		av_dump_format(输入引擎, 0, s输入路径, 0);// 将有关文件的信息转储到标准错误中
		int ret = 0;
		for (int i = 0; i < 输入引擎.nb_streams(); i++) {
			AVStream 输入流 = 输入引擎.streams(i);
			// stream.codecpar().codec_id();
			// stream.codecpar().codec_type()
			if (输入流.codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) {
				视频解码器 = step2_准备流解码器(i, 输入流);
				ret = 1;
			} else if (输入流.codecpar().codec_type() == AVMEDIA_TYPE_AUDIO) {
				音频解码器 = step2_准备流解码器(i, 输入流);
				ret = 2;
			}
		}
		if (ret == 0)
			error("缺少音频视频文件");
		// packet 用于存放从流中取到的帧
		this.packet = new AVPacket();
		this.packet2 = new AVPacket();
		this.frame = av_frame_alloc();// 用c 声明 分配帧内存
		// System.out.println(frame);

	}

	AVCodecContext step2_准备流解码器(int 流位置, AVStream 输入流) {
		AVCodecParameters inparam = 输入流.codecpar();
		int 类型 = inparam.codec_type();
		if (流位置 == -1) {
			log.info("找不到音频关键帧,在" + s输入路径);
			return null;
		}
		AVDictionary 配置项 = null;
		if (类型 == AVMEDIA_TYPE_VIDEO) {
			配置项 = 视频解码器配置项;
		} else if (类型 == AVMEDIA_TYPE_AUDIO) {
			配置项 = 音频解码器配置项;
		} else {
			log.info("不可识别的流");
			return null;
		}

		int 编码 = inparam.codec_id();
		// 查找解码器
		AVCodec 编码参数 = avcodec_find_decoder(编码);
		if (编码参数 == null) {
			log.info("未知的编码参数=" + 编码参数);
			return null;
		}

		// 从解码器获取解码器对象
		AVCodecContext 解码器 = avcodec_alloc_context3(编码参数);
		// 需要使用avcodec_free_context释放
		// 事实上codecpar包含了大部分解码器相关的信息，这里是直接从AVCodecParameters复制到AVCodecContext
		avcodec_parameters_to_context(解码器, 输入流.codecpar());
		// av_codec_set_pkt_timebase(pCodecCtx, stream.time_base);
		// 打开解码器
		if (avcodec_open2(解码器, 编码参数, 配置项) < 0) {
			log.info("无法打开音频解码器:编码=" + 编码);
			return null;
		}

		return 解码器;

	}

	public void step3_构造变形器() {
		frameRGB = av_frame_alloc();

		/*
		 * 分配一个AVFrame并将其字段设置为默认值。<br>
		 * 
		 * 结果必须使用av_frame_free（）释放结构。<br>
		 * 
		 * @返回一个用默认值填充的AVFrame或失败时为NULL。<br>
		 * 
		 * 注意这只分配AVFrame本身，而不分配数据缓冲区。<br>
		 * 
		 * 数据缓冲区通过使用av_frame_get_buffer（）或其他方式分配<br>
		 * 
		 * 
		 */
		if (frameRGB == null) {
			log.info("无法分配avframe");
			return;
		}
		frameRGB.width(i输出宽);
		frameRGB.height(i输出高);
		frameRGB.format(i图像输出格式);

		// av_image_get_buffer_size 函数的作用是通过指定像素格式、图像宽、图像高来计算所需的内存大小
		// 旧的写法 numBytes = avpicture_get_size(图像格式, width, height);
		int numBytes = av_image_get_buffer_size(i图像输出格式, i输出宽, i输出高, 1);
		// System.out.println("numBytes" + numBytes);
		// 确定图像所需的缓冲区大小, 并分配缓冲区
		/*
		 * 分配一个适合所有内存访问的对齐内存块（包括CPU上可用的向量）。
		 */
		BytePointer buffer = new BytePointer(av_malloc(numBytes));
		// 将缓冲区的适当部分分配给pframeRGB中的图像平面,把buffer 分配给pframeRGB.data()
		// 请注意，pframeRGB是一个AVFrame，但AVFrame是一个AVPicture的超集
		// 过期 avpicture_fill(new AVPicture(pframeRGB), buffer, 图像格式,
		// pCodecCtx.width(),pCodecCtx.height());
		avutil.av_image_fill_arrays(frameRGB.data(), frameRGB.linesize(), buffer, i图像输出格式, i输出宽, i输出高, 1);
		/*
		 * SwsContext sws_ctx 图像变换器 ,你需要它来表演缩放等操作
		 * 
		 * sws_getContext () 分配并返回一个SwsContext。
		 * 
		 * 使用sws_scale（）进行缩放/转换操作。
		 */
		图像变形器 = sws_getContext(i输出宽, i输出高, 视频解码器.pix_fmt(), i输出宽, i输出高, i图像输出格式, SWS_BILINEAR, null, null,
				(DoublePointer) null);
	}

	/**
	 * 开始遍历包,解码或转包输出
	 * 
	 * @throws Exception
	 */
	public void step4() throws Exception {

		int j = 0;// 视频抓帧索引位置
		boolean enable图像过滤 = true;
		boolean enable音频过滤 = true;
		boolean enable包过滤 = true;
		// pFrame 用于存放avpacket 解码后的 avframe

		is运行 = true;
		// 读视频帧一个帧
		while (is运行 && av_read_frame(输入引擎, packet) >= 0) {

			av_packet_ref(packet2, packet);
			int idx = packet.stream_index();
			AVStream steam = 输入引擎.streams(idx);
			int type = steam.codecpar().codec_type();
			// System.out.println("enable图像过滤="+enable图像过滤);
			// 如果是图像关键帧帧
			if (type == AVMEDIA_TYPE_VIDEO && enable图像过滤 && fl图像过滤器 != null) {

				// 时间基转换
				// AVRational time_base = av_inv_q(视频编码器.framerate());
				// av_packet_rescale_ts(packet, 视频流.time_base(), time_base);
				// 解码视频帧
				// 将AVPKT大小的视频帧从AVPKT>数据解码为图片。
				//// 一些解码器可以支持单个av包中的多个帧，例如解码器将只解码第一帧。
				// 函数 avcodec_decode_video2 已经过时了，现在推荐使用的是 avcodec_send_packet() 和
				// avcodec_receive_frame()，
				// 而在新版本中，avcodec_decode_video2 也是通过这两个新的 API 完成解码功能的。
				// 读取到一帧音频或者视频 packet
				int re = avcodec_send_packet(视频解码器, packet);
				if (re != 0)
					break;
				while (avcodec_receive_frame(视频解码器, frame) == 0) {
					// 处理解码后音视频 pFrame
					sws_scale(图像变形器, frame.data(), frame.linesize(), 0, frame.height(), frameRGB.data(),
							frameRGB.linesize());
					// 必须在里面处理因为 packet 是压缩模式,必须转成视频frame 以后才能识别
					// 另外根据i ,b,p帧 解压原理必须连续解压才可以识别
					if (j == 0) {
						// frameRGB 别用成了frame
						BufferedImage img = MyImage.avFrame2Img(frameRGB);
						//System.out.println("1");
						if (fl图像过滤器.where(img)) {
							// System.out.println("终止");

							fl图像过滤器 = null;
							enable图像过滤 = false;// 终止图像过滤
						}
					}
					j++;
					if (j == i视频间隔帧) {
						j = 0;
					}
				}
			} else if (type == AVMEDIA_TYPE_AUDIO && enable音频过滤 && fl音频帧过滤器 != null) {
				int re = avcodec_send_packet(音频解码器, packet);
				if (re != 0)
					break;
				while (avcodec_receive_frame(音频解码器, frame) == 0) {
					// 音频处理部分

					if (fl音频帧过滤器.isOver(steam, frame)) {
						enable音频过滤 = false;// 终止图像过滤
					}
				}
			}

			if (enable包过滤 && av包过滤器 != null && av包过滤器.where(packet2)) {
				//System.out.println("包过滤");
				enable包过滤 = false;
			}
			//System.out.println("-");
			av_packet_unref(packet);
			av_packet_unref(packet2);
		}
	}

	boolean is运行 = false;

	public void error(String 异常) throws Exception {
		release();
		throw new Exception(异常);
	}

	public void close() {
		is运行 = false;
		release();
	}

	public void release() {
		if (packet != null)
			av_packet_unref(packet);
		// 手动释放内存

		if (packet2 != null)
			av_packet_unref(packet2);
		// 手动释放内存

		if (frameRGB != null)
			av_free(frameRGB);// Free the RGB image

		if (frame != null)
			av_free(frame);

		if (图像变形器 != null)
			sws_freeContext(图像变形器);// Free SwsContext

		if (视频解码器 != null) {
			avcodec_free_context(视频解码器);
			avcodec_close(视频解码器);// Close the codec
		}
		if (音频解码器 != null) {
			avcodec_free_context(音频解码器);
			avcodec_close(音频解码器);// Close the codec
		}
		if (输入引擎 != null) {
			// 关闭图像源
			avformat_close_input(输入引擎);// 包含了 avformat_free_context(输入引擎);
		}

	}

}
